﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Bouyei.Geo.Geometries
{
    public class GeoLineSegment : Geometry
    {
        public GeoPoint Start { get { return GetSequence(0, 0).GetPoint(0); } }

        public GeoPoint End { get { return GetSequence(0, 0).GetPoint(1); } }

        public GeoLineSegment(Coordinate Start, Coordinate End)
            : base(GeoType.LineSegment, new Coordinate[] { Start, End })
        {

        }

        public GeoLineSegment(GeoPoint start, GeoPoint end)
            : base(GeoType.LineSegment, new GeoPoint[] { start, end })
        {

        }

        public double GeoLength()
        {
            return Start.Distance(End);
        }

        public GeoPoint GetCenter()
        {
            return new GeoPoint((Start.X + End.X) / 2, (Start.Y + End.Y) / 2);
        }

        /// <summary>
        /// 判断点在线段的位置,=0在线上,=1在左边,=-1在右边
        /// </summary>
        /// <param name="point"></param>
        /// <returns></returns>
        public int PointOfLineSegment(GeoPoint point)
        {
            double temp = (Start.Y - End.Y) * point.X
                + (End.X - Start.X) * point.Y
                + Start.X * End.Y
                - End.X * Start.Y;

            if(temp==0.0)
            {
                return 0;
            }else if(temp>0)
            {
                return 1;
            }
            else
            {
                return -1;
            }
        }

        public double MultiplicationCross(double x1, double y1, double x2, double y2)
        {
            return x1 * y2 - x2 * y1;
        }

        public double MultiplicationCross(Coordinate s,Coordinate e)
        {
            return MultiplicationCross(s.X, s.Y, e.X, e.Y);
        }
        public double MultiplicationCross(GeoPoint s, GeoPoint e)
        {
            return MultiplicationCross(s.X, s.Y, e.X, e.Y);
        }

        /// <summary>
        /// 点到线段距离
        /// </summary>
        /// <param name="point"></param>
        /// <returns></returns>
        public double PointToLineSegment(GeoPoint point)
        {
            //起止同一点
            if (Start.X == End.X && Start.Y == End.Y)
                return point.Distance(Start);

            double _x = End.X - Start.X;
            double _y = End.Y - Start.Y;

            double len2 = Math.Pow(_x, 2) + Math.Pow(_y, 2);
            double r = ((point.X - Start.X) * _x + (point.Y - Start.Y) * _y) / len2;

            //点在线段上或延长线上
            if (r <= 0.0)
                return point.Distance(Start);
            if (r >= 1.0)
                return point.Distance(End);

            double s = ((Start.Y - point.Y) * (_x) - (Start.X - point.X) * (_y)) / len2;
            return Math.Abs(s) * Math.Sqrt(len2);
        }

        /// <summary>
        /// 共线
        /// </summary>
        /// <param name="lineSegment"></param>
        /// <returns></returns>
        public bool IsCollinear(GeoLineSegment lineSegment)
        {
            return MultiplicationCross(Start.X-End.X,Start.Y-End.Y,
                Start.X-lineSegment.End.X,Start.Y-lineSegment.End.Y) == 0;
        }

        /// <summary>
        /// 是否共线
        /// </summary>
        /// <param name="lineSegment"></param>
        /// <returns></returns>
        public bool IsParallel(GeoLineSegment lineSegment)
        {
            return MultiplicationCross(Start.X - End.X, Start.Y - End.Y,
               lineSegment.Start.X - lineSegment.End.X, lineSegment.Start.Y - lineSegment.End.Y) == 0;
        }

        public bool DiagonalIntersects(GeoLineSegment lineSegment)
        {
            return (lineSegment.Start < End)
               && (lineSegment.End > Start);
        }

        public bool IsIntersects(GeoLineSegment lineSegment)
        {
            return false;
        }

        public bool Intersects(GeoLineSegment lineSegment,ref GeoPoint crossPoint,int precision=0)
        {
            //abc面积
            double area_abc = (Start.X - lineSegment.Start.X) * (End.Y - lineSegment.Start.Y)
                - (Start.Y - lineSegment.Start.Y) * (End.X - lineSegment.Start.X);

            //abd面积
            double area_abd = (Start.X - lineSegment.End.X) * (End.Y - lineSegment.End.Y)
                - (Start.Y - lineSegment.End.Y) * (End.X - lineSegment.End.X);
            
            if(precision>0)
            {
                area_abc = Math.Round(area_abc, precision);
                area_abd = Math.Round(area_abd, precision);
            }
            if (area_abc * area_abd >= 0) return false;

            //cda面积
            double area_cda = (lineSegment.Start.X - Start.X) * (lineSegment.End.Y - Start.Y)
                - (lineSegment.Start.Y - Start.Y) * (lineSegment.End.X - Start.Y);

            //cdb面积
            double area_cdb = area_cda + area_abc - area_abd;
            if(precision>0)
            {
                area_cdb = Math.Round(area_cdb, precision);
            }

            if (area_cda * area_cdb >= 0) return false;

            double cross = area_cda / (area_abd - area_abc);

            double dx = cross * (End.X - Start.X);
            double dy = cross * (End.Y - Start.Y);
            crossPoint = new GeoPoint(Start.X + dx, Start.Y + dy);

            return true;
        }

        public GeoPoint Intersects(GeoLineSegment lineSegment)
        {
            var s1 = Start;
            var s2 = End;

            var d1 = lineSegment.Start;
            var d2 = lineSegment.End;

            double b1 = (s2.Y - s1.Y) * s1.X + (s1.X - s2.X) * s1.Y;
            double b2 = (d2.Y - d1.Y) * d1.X + (d1.X - d2.X) * d1.Y;

            double d = (s2.X - s1.X) * (d2.Y - d1.Y) - (d2.X - d1.X) * (s2.Y - s1.Y);

            double x1 = b2 * (s2.X - s1.X) - b1 * (d2.X - d1.X);
            double y1 = b2 * (s2.Y - s1.Y) - b1 * (d2.Y - d1.Y);

            //z

            return new GeoPoint(x1 / d, y1 / d);
        }

        /// <summary>
        /// 对角线
        /// </summary>
        /// <returns></returns>
        public GeoLineSegment GetDiagonal()
        {
            var min = Start.Min(End);
            var max = Start.Max(End);

            return new GeoLineSegment(min, max);
        }

        public bool Contains(GeoPoint point)
        {
            var min = Start.Min(End);
            var max = Start.Max(End);

            if (point < min || point > max) return false;
            double sub = ((point.X - Start.X) * (Start.Y - End.Y))- ((point.Y - Start.Y) * (Start.X - End.X));

            return sub == 0;
        }

        public bool Contains(Coordinate coord)
        {
            var end = new GeoPoint(coord);
            var min = Start.Min(end);
            var max = Start.Max(end);

            if (end < min || end > max) return false;
            double sub = ((coord.X - Start.X) * (Start.Y - End.Y))- ((coord.Y - Start.Y) * (Start.X - End.X));

            return sub == 0;
        }
    }
}
